package kayao.client.network.login;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.Enumeration;
import java.util.Vector;

import kayao.common.*;
import kayao.client.LoginListener;
import kayao.client.data.SharedMemory;
import kayao.client.network.SynchronizedTime;
import kayao.client.network.message.MessageHandler;

public class LoginThread extends Thread {
	private static final int NUMBER_OF_RETRIES = 5;
	private static final int TIMEOUT_MILLISECONDS = 5000;
	
	private LoginListener mLoginListener;
	private String mName;
	
	private SynchronizedTime mTime;
	
	public LoginThread(String name) {
		mName = name;
	}
	
	@Override
	public void run() {
		try {
			// Initialize the socket and the packets.
			InetAddress ipServer = InetAddress.getByName(KayaoValues.SERVER_IP);
			DatagramSocket socket = new DatagramSocket(KayaoValues.CLIENT_LOGIN_PORT);
			socket.setSoTimeout(TIMEOUT_MILLISECONDS);
			DatagramPacket sendPacket = new DatagramPacket(new byte[1], 1, ipServer, KayaoValues.SERVER_PORT);
			final int receivePacketSize = maxReceivePacketSizeByte();
			DatagramPacket receivePacket = new DatagramPacket(new byte[receivePacketSize], receivePacketSize);	
			
			createPacket(sendPacket);
			
			int mAttempts = 0;
			boolean mTimeout = true;
			while (mTimeout && mAttempts < NUMBER_OF_RETRIES) {
				try {
					socket.send(sendPacket);
					MessageHandler.statistics(KayaoValues.PLAYING_REQUEST_MESSAGE);
				} catch (IOException e) {
					e.printStackTrace();
				}
				try {
					socket.receive(receivePacket);
					MessageHandler.statistics(KayaoValues.PLAYING_REQUEST_REPLY_MESSAGE);
					mTimeout = false;
				} catch (IOException e) {
					// Socket timed out
					mAttempts++;
				}
			}
		
			
			if (mAttempts != NUMBER_OF_RETRIES) {
				processResponse(receivePacket);
				
				// Reset flags
				mAttempts = 0;
				mTimeout = true;
				// Ask the server what time it is
				while (mTimeout && mAttempts < NUMBER_OF_RETRIES) {
					SynchronizedTime.askForTime(sendPacket);
					try {
						socket.send(sendPacket);
						MessageHandler.statistics(KayaoValues.WHAT_TIME_IS_IT_MESSAGE);
					} catch (IOException e) {
						e.printStackTrace();
					}
					try {
						socket.receive(receivePacket);
						MessageHandler.statistics(KayaoValues.TIME_IS_MESSAGE);
						mTimeout = false;
					} catch (IOException e) {
						// Socket timed out
						mAttempts++;
					}
					mTimeout = false;
					SynchronizedTime.setTime(receivePacket);
				}
				System.out.println("Server time: "+ SynchronizedTime.getTime());
				
				//Tell teammates that I exist
				NotifyImNewTeamMate ntm=new NotifyImNewTeamMate(SharedMemory.getInstance().getTeamMates(), socket);
				ntm.tell();
				
				socket.close();
				mLoginListener.onLogin();
			} else {
				socket.close();
				throw new Exception();
			}
			
		} catch (Exception e) {
			e.printStackTrace();
			mLoginListener.onLoginFailed("Failed to log in to server: " + e.getMessage());
		}
	}
	
	private void createPacket(DatagramPacket sendPacket) {
		// 1 byte message id + 1 byte name length + 1 byte for each character in the name
		byte[] sendData = new byte[2 + mName.length()];
		
		// Insert message id
		sendData[0] = KayaoValues.PLAYING_REQUEST_MESSAGE;
		// Number of letters in name
		sendData[1] = (byte)mName.length();
		// Letters in name
		for (int i = 0; i < mName.length(); i++)
			sendData[2 + i] = (byte)mName.charAt(i);
		
		sendPacket.setData(sendData);
	}
	
	private void processResponse(DatagramPacket receivePacket) {
		byte[] receiveData = receivePacket.getData();
		int index = 0;
		
		// Get message id
		int intData = NetworkUtils.unsignedByteToInt(receiveData[index]); //TODO: verify msgid?
		index++;
		
		// Get magic number
		intData = NetworkUtils.insertByteArrayToInt(receiveData, index);
		index += 4;
		KayaoValues.MAGIC_NUMBER = intData;
		
		// Get id of local client
		intData = NetworkUtils.insertByteArrayToInt(receiveData, index);
		index += 4;
		SharedMemory.getInstance().setLocalId(intData);
		
		// Get ip of local client
		String ip = "";
		for (int i=0; i<4; i++){
			ip += NetworkUtils.unsignedByteToInt(receiveData[index+i]);
			if (i<3){ ip +=".";	}
		}
		System.out.println("*** MY OWN IP: "+ip);
		index += 4;
		
		// Get team id
		intData = NetworkUtils.unsignedByteToInt(receiveData[index]);
		SharedMemory.getInstance().setLocalTeamId(intData);
		index++;
		
		// Get number of teammates
		short numberOfTeammates = NetworkUtils.insertByteArrayToShort(receiveData, index);
		index += 2;
		
		for (short i = 0; i < numberOfTeammates; i++) {
			// Get client id
			intData = NetworkUtils.insertByteArrayToInt(receiveData, index);
			index += 4;
			
			// Get client ip (4x(b))
			final int first = NetworkUtils.unsignedByteToInt(receiveData[index]);
			final int second = NetworkUtils.unsignedByteToInt(receiveData[index + 1]);
			final int third = NetworkUtils.unsignedByteToInt(receiveData[index + 2]);
			final int fourth = NetworkUtils.unsignedByteToInt(receiveData[index + 3]);
			index += 4;
			
			// Get length of client name
			final int nameLength = NetworkUtils.unsignedByteToInt(receiveData[index]);
			index++;
			
			// Get client name
			String clientName = "";
			for (int j = 0; j < nameLength; j++) {
				clientName += (char)receiveData[index];
				index ++;
			}
			
			try {
				InetAddress IPClient = InetAddress.getByName(first+"."+second+"."+third+"."+fourth);  // Temp
				SharedMemory.getInstance().addClient(new Client(clientName, intData, IPClient.toString().substring(1), SharedMemory.getInstance().getLocalTeamId()));
			} catch (UnknownHostException e) {
				e.printStackTrace();
			}// Can remove try catch after InetAddress is gone.
		}
		//Create Ourselves
		
		try
		{
//			String ownIP=getMyIP();
		    SharedMemory.getInstance().setClient(new Client(mName,
						SharedMemory.getInstance().getLocalId(),ip,SharedMemory.getInstance().getLocalTeamId()));
			SharedMemory.getInstance().addClient(SharedMemory.getInstance().getMyself());
		}
		catch (Exception e)
		{
		      System.out.println("Exception caught ="+e.getMessage());
		}
		
//		// Get coordinates for avatar starting position
//		float avatarPosX = NetworkUtils.insertByteArrayToFloat(receiveData, index);
//		index += 4;
//		float avatarPosY = NetworkUtils.insertByteArrayToFloat(receiveData, index);
//		index += 4;
//		float avatarPosZ = NetworkUtils.insertByteArrayToFloat(receiveData, index);
//		index += 4;
//		//TODO: Process avatar information
//
//		// Get coordinates for avatar starting position
//		float flagPosX = NetworkUtils.insertByteArrayToFloat(receiveData, index);
//		index += 4;
//		float flagPosY = NetworkUtils.insertByteArrayToFloat(receiveData, index);
//		index += 4;
//		float flagPosZ = NetworkUtils.insertByteArrayToFloat(receiveData, index);
//		index += 4;
//		SharedMemory.getInstance().createMissionData(flagPosX, flagPosY, flagPosZ);
	}
	
	private String getMyIP() {
		Enumeration<NetworkInterface> e1;
		Vector<InetAddress> ads= new Vector<InetAddress>();
		InetAddress ownIP=null;
		try {
			e1 = (Enumeration<NetworkInterface>)NetworkInterface.getNetworkInterfaces();
			while(e1.hasMoreElements()) {
				NetworkInterface ni = e1.nextElement();
				Enumeration<InetAddress> e2 = ni.getInetAddresses();
				while(e2.hasMoreElements()) {
					InetAddress ia = e2.nextElement();
					ads.add(ia);
				}
			}
			for(int i=0;i<ads.size();i++)
			{
				if(ads.get(i).toString().substring(1).startsWith("127"))
					continue;
				else ownIP=ads.get(i);
			}
		} catch (SocketException e3) {
			// TODO Auto-generated catch block
			e3.printStackTrace();
		}
		return ownIP.getHostAddress();
	}

	private int maxReceivePacketSizeByte() {
		return 1000; //TODO: Calculate
	}
	
	public void setLoginListener(LoginListener loginListener) {
		mLoginListener = loginListener;
	}
}
